package org.qiaice.util;

import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import io.jsonwebtoken.security.SignatureException;
import org.qiaice.entity.Employee;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

@Component
public class TokenUtil {

    // 加密/解密 密钥
    private static String secret;

    @Value(value = "${token.secret}")
    private void setSecret(String secret) {
        TokenUtil.secret = secret;
    }

    // token 签发者
    private static String issuer;

    @Value(value = "${token.issuer}")
    private void setIssuer(String issuer) {
        TokenUtil.issuer = issuer;
    }

    // token 主题
    private static String subject;

    @Value(value = "${token.subject}")
    private void setSubject(String subject) {
        TokenUtil.subject = subject;
    }

    // token 有效时长
    private static String expiration;

    @Value(value = "${token.expiration}")
    private void setExpiration(String expiration) {
        TokenUtil.expiration = expiration;
    }

    // token 有效时长
    private static String refresh;

    @Value(value = "${token.refresh}")
    private void setRefresh(String refresh) {
        TokenUtil.refresh = refresh;
    }

    // 需要跳过 token 验证的 url
    public static Set<String> skips;

    @Value(value = "${token.skips}")
    private void setSkips(String skips) {
        TokenUtil.skips = Arrays.stream(skips.split(",")).collect(Collectors.toSet());
    }

    private TokenUtil() {
    }

    // token 的默认头部
    private static final Map<String, String> DEFAULT_HEADERS = Map.of("typ", "JWT");

    // token 过期时间的正则表达式
    private static final Pattern EXPIRATION_PATTERN = Pattern.compile("(\\d+)(ms|s|m|h|d|w|M)?");

    /**
     * 使用员工信息创建 token
     * @param employee 员工信息
     * @param isRefresh 创建的 token 是否是 refresh token
     * @return token
     */
    public static String createToken(Employee employee, boolean isRefresh) {
        Map<String, ?> payload = Map.of("id", employee.getId(), "username", employee.getUsername());
        return isRefresh ? createRefreshToken(payload) : createToken(payload);
    }

    /**
     * 使用默认头部和自定义负载创建 token
     *
     * @param payload 自定义负载
     * @return token
     */
    private static String createToken(Map<String, ?> payload) {
        return createToken(DEFAULT_HEADERS, payload, expiration);
    }

    /**
     * 使用默认头部和自定义负载创建 refresh token
     *
     * @param payload 自定义负载
     * @return token
     */
    private static String createRefreshToken(Map<String, ?> payload) {
        return createToken(DEFAULT_HEADERS, payload, refresh);
    }

    /**
     * 使用自定义头部和自定义负载创建 token
     *
     * @param header  自定义头部
     * @param payload 自定义负载
     * @return token
     */
    public static String createToken(Map<String, ?> header, Map<String, ?> payload, String expiration) {
        return Jwts.builder()
                .header()
                .add(header) // 添加头部
                .and()
                .claims(payload) // 添加负载
                .id(UUID.randomUUID().toString()) // 这里的键是 jti 不是 id
                .issuer(issuer)
                .subject(subject)
                .issuedAt(convert2Date("0"))
                .expiration(convert2Date(expiration))
                .signWith(Keys.hmacShaKeyFor(secret.getBytes()), Jwts.SIG.HS512)
                .compact();
    }

    /**
     * 解析配置文件中的过期时间设置，并以 Date 对象返回
     *
     * @param exp 字符串格式的过期时间
     * @return Date 对象格式的过期时间
     */
    private static Date convert2Date(String exp) {
        Matcher matcher = EXPIRATION_PATTERN.matcher(exp);
        if (!matcher.matches()) throw new RuntimeException("暂不支持该种时间单位: " + exp);
        Calendar calendar = Calendar.getInstance();
        calendar.add(switch (matcher.group(2)) {
            case "s" -> Calendar.SECOND;
            case "m" -> Calendar.MINUTE;
            case "h" -> Calendar.HOUR;
            case "d" -> Calendar.DAY_OF_YEAR;
            case "w" -> Calendar.WEEK_OF_YEAR;
            case "M" -> Calendar.MONTH;
            case null, default -> Calendar.MILLISECOND;
        }, Integer.parseInt(matcher.group(1)));
        return calendar.getTime();
    }

    /**
     * 解析 token 并返回获得的数据
     *
     * @param token token
     */
    public static Jws<Claims> parseToken(String token) {
        try {
            return Jwts.parser()
                    .verifyWith(Keys.hmacShaKeyFor(secret.getBytes()))
                    .build()
                    .parseSignedClaims(token);
        } catch (ExpiredJwtException | SignatureException | MalformedJwtException e) {
            throw new RuntimeException("无效的 token");
        }
    }
}
